package org.voidness.oje2d;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import javax.imageio.ImageIO;

import org.lwjgl.opengl.GL11;

/** This holds a GL texture to use with images */
public class GLTexture {
    /** The texture array of memory values */
    private int texture[];

    /** The width of the texture */
    private int width;

    /** The height of the texture */
    private int height;

    /** Whether this texture is tilable */
    private boolean tile;

    /**
     * A constructor for the texture.
     * 
     * @param mFilename The path for the image to turn into texture
     * @param mTile Whether we want to tile this texture or not
     */
    public GLTexture(String mFilename, boolean mTile) {
        texture = new int[1];
        tile = mTile;
        loadGLTexture(mFilename);
    }

    /**
     * A constructor for the texture, based on another texturem.
     * 
     * @param mTexture The texture to use
     * @param mTile Whether we want to tile this texture or not
     */
    public GLTexture(int[] mTexture, boolean mTile) {
        texture = mTexture;
        tile = mTile;
    }

    /**
     * Another constructor.
     * 
     * @param mImage The image to turn into texture
     * @param mTile Whether we want to tile this texture or not
     */
    public GLTexture(Image mImage, boolean mTile) {
        texture = new int[1];
        tile = mTile;
        loadGLTexture(mImage);
    }

    /**
     * Get the texture's width.
     * 
     * @return The width value
     */
    public int getWidth() {
        return width;
    }

    /**
     * Get the texture's height.
     * 
     * @return The height value
     */
    public int getHeight() {
        return height;
    }

    /**
     * Binds the texture in OpenGL.
     */
    public void bind() {
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture[0]);
    }

    /**
     * Private method to generate texture from an image path.
     * 
     * @param mPath The image's path
     * @return An integer with the memory address for this new texture
     */
    private int getTexture(String mPath) {
        BufferedImage image = null;
        try {
            image = ImageIO.read(new File(mPath));
        } catch (FileNotFoundException e) {
            // System.out.println("File not found: " + mPath);
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (image == null || image.getWidth() < 0) {
            Utils.dump("Error reading image: " + mPath, Utils.ERROR);
            System.exit(1);
        }

        // Exctract The Image
        BufferedImage bImage =
                new BufferedImage(image.getWidth(), image.getWidth(),
                        BufferedImage.TYPE_4BYTE_ABGR);
        Graphics2D g = (Graphics2D) bImage.getGraphics();
        g.drawImage(image, null, null);
        g.dispose();

        // Flip Image
        AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
        tx.translate(0, -image.getHeight(null));
        AffineTransformOp op =
                new AffineTransformOp(tx,
                        AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        bImage = op.filter(bImage, null);

        Raster r = bImage.getRaster();
        // pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
        width = r.getWidth();
        height = r.getHeight();

        // Put Image In Memory
        ByteBuffer scratch =
                ByteBuffer.allocateDirect(4 * bImage.getWidth()
                        * bImage.getHeight());

        byte[] data =
                (byte[]) bImage.getRaster().getDataElements(0, 0,
                        bImage.getWidth(), bImage.getHeight(), null);
        scratch.clear();
        scratch.put(data);
        scratch.rewind();

        // Create A IntBuffer For Image Address In Memory
        IntBuffer buf =
                ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder())
                        .asIntBuffer();
        GL11.glGenTextures(buf); // Create Texture In OpenGL

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
        // Typical Texture Generation Using Data From The Image

        if (tile) {
            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S,
                    GL11.GL_REPEAT);
            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T,
                    GL11.GL_REPEAT);
        }

        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
                GL11.GL_LINEAR);
        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
                GL11.GL_LINEAR);
        // Generate The Texture
        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, bImage
                .getWidth(), bImage.getHeight(), 0, GL11.GL_RGBA,
                GL11.GL_UNSIGNED_BYTE, scratch);

        return buf.get(0); // Return Image Address In Memory
    }

    /**
     * Method to generate texture from a buffered image.
     * 
     * @param mImage The image to turn into texture
     * @return An integer with the image's memory address
     */
    private int getTextureFromBI(Image mImage) {
        // Exctract The Image
        BufferedImage tex =
                new BufferedImage(mImage.getWidth(null),
                        mImage.getHeight(null), BufferedImage.TYPE_4BYTE_ABGR);
        Graphics2D g = (Graphics2D) tex.getGraphics();
        g.drawImage(mImage, null, null);
        g.dispose();

        // Flip Image
        AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
        tx.translate(0, -mImage.getHeight(null));
        AffineTransformOp op =
                new AffineTransformOp(tx,
                        AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        tex = op.filter(tex, null);

        // Put Image In Memory
        ByteBuffer scratch =
                ByteBuffer.allocateDirect(4 * tex.getWidth() * tex.getHeight());

        byte data[] =
                (byte[]) tex.getRaster().getDataElements(0, 0, tex.getWidth(),
                        tex.getHeight(), null);
        scratch.clear();
        scratch.put(data);
        scratch.rewind();

        // Create A IntBuffer For Image Address In Memory
        IntBuffer buf =
                ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder())
                        .asIntBuffer();
        GL11.glGenTextures(buf); // Create Texture In OpenGL

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
        // Typical Texture Generation Using Data From The Image

        if (tile) {
            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S,
                    GL11.GL_REPEAT);
            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T,
                    GL11.GL_REPEAT);
        }

        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
                GL11.GL_LINEAR);
        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
                GL11.GL_LINEAR);
        // Generate The Texture
        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, tex.getWidth(),
                tex.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE,
                scratch);

        return buf.get(0); // Return Image Address In Memory
    }

    /**
     * Reloads all texture data
     */
    public void reloadData() {
        BufferedImage img =
                new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        // img.getRaster().setPixels(0, 0, width, height, pixels);

        ByteBuffer scratch =
                ByteBuffer.allocateDirect(4 * img.getWidth() * img.getHeight());

        byte data[] =
                (byte[]) img.getRaster().getDataElements(0, 0, img.getWidth(),
                        img.getHeight(), null);
        scratch.clear();
        scratch.put(data);
        scratch.rewind();

        // Create A IntBuffer For Image Address In Memory
        IntBuffer buf =
                ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder())
                        .asIntBuffer();
        GL11.glGenTextures(buf); // Create Texture In OpenGL

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
        // Typical Texture Generation Using Data From The Image

        if (tile) {
            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S,
                    GL11.GL_REPEAT);
            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T,
                    GL11.GL_REPEAT);
        }

        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
                GL11.GL_LINEAR);
        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
                GL11.GL_LINEAR);
        // Generate The Texture
        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height,
                0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, scratch);

        texture[0] = buf.get(0);
    }

    /**
     * This method loads a texture based on the image's path.
     * 
     * @param mPath The path to the image to load
     */
    public void loadGLTexture(String mPath) {
        // make room for 1 textures/filters
        // GL11.glGenTextures(IntBuffer.wrap(texture));

        // Create Nearest Filtered Texture
        // GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture[0]);
        // GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
        // GL11.GL_NEAREST);
        // GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
        // GL11.GL_NEAREST);

        if (mPath.endsWith(".png") || mPath.endsWith(".bmp")
                || mPath.endsWith(".jpg"))
            texture[0] = getTexture(mPath);
        else {
            Utils.dump("Image not supported (only PNGs, JPGs and BMPs): "
                    + mPath, Utils.ERROR);
            System.exit(1);
        }
    }

    /**
     * This method loads a texture based on an awt.Image.
     * 
     * @param mImage The image to load
     */
    public void loadGLTexture(Image mImage) {
        // make room for 1 textures/filters
        // GL11.glGenTextures(IntBuffer.wrap(texture));

        // Create Nearest Filtered Texture
        // GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture[0]);
        // GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
        // GL11.GL_NEAREST);
        // GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
        // GL11.GL_NEAREST);

        texture[0] = getTextureFromBI(mImage);
    }

    /**
     * Clones this object
     * 
     * @return A new texture instance
     */
    public Object clone() {
        return new GLTexture((int[]) texture.clone(), tile);
    }
}